package pjaws;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import aws.EC2;

import pjaws.paw.Paw;
import pjaws.paw.PawSocket;

/**
 * 
 * Sample Application
 * 
 * 1. Construct root
 * <p>
 * 2. Add a paw value that pairs to a java file that defines a paw program
 * <p>
 * 3. Create config file for each paw. particularly match paw id's
 * <p>
 * 4. Create an EC2 instance for each paw
 * <p>
 * 5. Copy paw files over to instance (*.java and *.paw files)
 * <p>
 * 6. start root
 * <p>
 * 7. start the paws
 * <p>
 * 8. terminate paws
 * 
 * @author Zachary Cancio
 * 
 */

public abstract class Pjaws {

	private Map<Integer, Paw> myPaws;
	private Root myRoot;
	// TODO confusing name, rename
	private Map<Integer, PjawsInstance> myPjawsInstances;
	private EC2 myEC2;
	private PjawsShell myPjawsShell;

	protected boolean isRoot = true;	

	// ---------CONTSRUCTORS-----------//
	public Pjaws() {
		initialize();
		try {
			// TODO fix so works on nats...
			InetAddress addr = InetAddress.getLocalHost();
			// Get hostname
			String hostname = addr.getHostAddress();
			initRoot(hostname, PjawsConstants.ROOTPORT);
		} catch (UnknownHostException e) {
		}
	}

	public Pjaws(String rootHostname, int rootPort) {
		initialize();
		initRoot(rootHostname, rootPort);
	}

	// -------INITIALIZATION METHODS -----//
	private void initialize() {
		myPaws = new HashMap<Integer, Paw>();
		myRoot = new Root();
		myPjawsInstances = new HashMap<Integer, PjawsInstance>();
		myPjawsShell = new PjawsShell();
	}

	public void initRoot(String rootHostname, int rootPort) {
		myRoot.setHostnameAndPort(rootHostname, rootPort);
	}

	// --------ACCESS METHODS-------//
	/**
	 * Add a paw to the map of maps. Also, initialize the statuses of the paws
	 * within root.
	 * 
	 * @param pawId
	 *            the pawId of the paw instance
	 * @param p
	 *            the paw instance
	 * 
	 */
	public void addPaw(int pawId, Paw p) {
		myPaws.put(pawId, p);
		// possible code smell =P
		// initialize the statuses of each paw to busy
		myRoot.addPawReceiveStatus(pawId, RootPawHandler.Status.BUSY);
		myRoot.addPawSendStatus(pawId, RootPawHandler.Status.BUSY);
	}

	public void setEC2(EC2 ec2) {
		myEC2 = ec2;
	}

	public void run(String[] args) {
		if (args.length == 0) {
			return;
		}
		String option = args[0];

		// setup and run root if option is -r
		if (option.equals("-r")) {

			// 1A. Create each EC2 instance and pair with PaW
			for (Paw p : myPaws.values()) {
				String id = myEC2.createInstance(PjawsConstants.IMAGE_ID);
				PjawsInstance pi = new PjawsInstance(PjawsConstants.PAWPORT, id);
				myPjawsInstances.put(p.getPawId(), pi);
			}

			// 1B. Wait for each instance to be ready, then save hostname
			for (Paw p : myPaws.values()) {
				boolean moveon = false;
				PjawsInstance pi = myPjawsInstances.get(p.getPawId());
				String id = pi.getEC2InstanceID();
				do {
					// check to see if the instance is not running and then if
					// the hostname is not reachable.
					if (myEC2.waitRunning(id) && myEC2.isHostnameReachable(id)){
						String hostname = myEC2.getInstanceHostnameById(id);
						System.out.println("hostname: " + hostname);
						// set hostname because it's valid.
						pi.setHostname(hostname);
						moveon = true;	
					} else {
						System.out.println("RECREATING INSTANCE for PaW " + p.getPawId());
						// kill instance 
						myEC2.terminateInstance(id);
						// make new instance
						id = myEC2.createInstance(PjawsConstants.IMAGE_ID);
						// replace pjaws instance
						pi = new PjawsInstance(PjawsConstants.PAWPORT, id);
						myPjawsInstances.put(p.getPawId(), pi);
					}
				} while (moveon == false);

			}

			// 2. go through each paw and set global initializations
			for (Paw p : myPaws.values()) {
				// initialize root
				String rootHostname = myRoot.getHostname();
				int rootPort = myRoot.getPort();
				p.setRootHostnameAndPort(rootHostname, rootPort);
				// go through each PawSocket and define the hostname & port
				for (PawSocket ps : p.getPawSockets().values()) {
					PjawsInstance pi = myPjawsInstances.get(ps.getOtherPawId());
					ps.setHostname(pi.getHostname());
					ps.setPort(pi.getPort());
				}

			}

			// 2B. Verification
			for (Paw p : myPaws.values()) {
				System.out.println("PaW " + p.getPawId());
				// check root
				System.out.println("Root Socket Info: " + p.getRootHostname()
						+ ": " + p.getRootPort());
				// go through each PawSocket and define the hostname & port
				for (PawSocket ps : p.getPawSockets().values()) {
					System.out.println(ps);
				}
			}

			// 3. generate paw initialization file and scp
			String userDir = System.getProperty("user.dir");

			for (Paw p : myPaws.values()) {
				// destination
				String hostname = myPjawsInstances.get(p.getPawId())
						.getHostname();
				String destination = "root@" + hostname + ":"
						+ PjawsConstants.REMOTE_DIR_PATH + ".";

				// init paw file.
				String initname = "PaW" + p.getPawId() + ".paw";
				p.generateInitFile(initname);
				String initfile = userDir + "/" + initname;
				myPjawsShell.addSCPPair(initfile, destination);

				// pjaws class file
				String pjawsname = getClass().getCanonicalName() + ".class";
				String pjawsfile = getClass().getProtectionDomain()
						.getCodeSource().getLocation().getFile()
						+ pjawsname;
				myPjawsShell.addSCPPair(pjawsfile, destination);

				// pjaws.jar
				myPjawsShell.addSCPPair(PjawsConstants.JAR_PATH, destination);

				// run script
				myPjawsShell
						.addSCPPair(PjawsConstants.SCRIPT_PATH
								+ PjawsConstants.SCRIPT_NAME, "root@"
								+ hostname + ":.");

				// all paw class files
				for (Paw p2 : myPaws.values()) {
					myPjawsShell.addSCPPair(p2.getClassFilepath(), destination);
				}

			}

			// 4. Sleep for 30 seconds so that instance is reaaallly ready.
			try {
				int seconds = 30;
				System.out.print("Sleeping for " + seconds + "seconds:");
				for (int i = 0; i < seconds; i++) {
					Thread.sleep(1000);
					System.out.print("#");
				}
				System.out.println("");

			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			// 5. mkdir pjaws dir
			for (Paw p : myPaws.values()) {
				String hostname = "root@"
						+ myPjawsInstances.get(p.getPawId()).getHostname();
				myPjawsShell.execCmd("ssh " + hostname + " mkdir "
						+ PjawsConstants.REMOTE_DIR_NAME);

			}

			// 5. execute scps
			myPjawsShell.runSCPs();

			// 6. start root
			myRoot.start();
			// 7. start each paw
			for (Paw p : myPaws.values()) {
				System.out.println("Starting PaW" + p.getPawId());
				String hostname = "root@"
						+ myPjawsInstances.get(p.getPawId()).getHostname();
				String name = this.getClass().getCanonicalName();
				myPjawsShell.execCmd("ssh " + hostname + " chmod 777 "
						+ PjawsConstants.SCRIPT_NAME);
				myPjawsShell.execCmd("ssh " + hostname + " ./"
						+ PjawsConstants.SCRIPT_NAME + " "
						+ PjawsConstants.REMOTE_DIR_PATH + " " + name + " "
						+ p.getPawId());

			}

			// TODO 9. clean up when done
			// myEC2.terminateAllIstances();

		}
		// start paw based on id. the init file must be in the current user
		// directory and named PaW(id).paw, e.g. PaW1.paw for pawId of 1
		else if (option.equals("-i")) {
			String userDir = System.getProperty("user.dir");
			String initFile = "";

			if (args.length == 1) {
				System.out.println("No Paw Id specified");
				return;
			}
			// get pawId from second argument
			int pawId = Integer.parseInt(args[1]);
			initFile = userDir + "/PaW" + pawId + ".paw";
			System.out.println(initFile);

			// get the paw
			Paw p = myPaws.get(pawId);
			// initialize the paw
			p.initFromFile(initFile);
			// start paw
			p.start();
			try {
				p.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}
}
